# -*-coding:utf-8-*-
import subprocess
import io
from enum import Enum


class AdbInterface:
    """ adb tools """

    class KeyCode(Enum):
        """按键"""
        NONE = 0
        UNKNOWN = NONE
        SOFT_LEFT = 1  # ？？？
        SOFT_RIGHT = 2  # ？？？
        TAG_LAST_KEYCODE = 85  # ？？？
        """ 手机操作 """
        HOME = 3
        BACK = 4
        CALL = 5
        ENDCALL = 6
        VOL_UP = 24
        VOL_DOWN = 25
        POWER = 26
        CAMERA_CAP = 27
        CAMERA_FOCUS = 80
        MENU = 82
        NOTIFICATION = 83
        SEARCH = 84
        MIC_MUTE = 91
        VOL_MUTE = 164
        """ 普通键盘 """
        KB_0 = 7
        KB_1 = 8
        KB_2 = 9
        KB_3 = 10
        KB_4 = 11
        KB_5 = 12
        KB_6 = 13
        KB_7 = 14
        KB_8 = 15
        KB_9 = 16
        KB_STAR = 17  # *
        KB_POUND = 18  # #
        KB_UP = 19
        KB_DOWN = 20
        KB_LEFT = 21
        KB_RIGHT = 22
        KB_CENTER = 23
        KB_CLEAR = 28
        KB_A = 29
        KB_B = 30
        KB_C = 31
        KB_D = 32
        KB_E = 33
        KB_F = 34
        KB_G = 35
        KB_H = 36
        KB_I = 37
        KB_J = 38
        KB_K = 39
        KB_L = 40
        KB_M = 41
        KB_N = 42
        KB_O = 43
        KB_P = 44
        KB_Q = 45
        KB_R = 46
        KB_S = 47
        KB_T = 48
        KB_U = 49
        KB_V = 50
        KB_W = 51
        KB_X = 52
        KB_Y = 53
        KB_Z = 54
        KB_COMMA = 55  # ,
        KB_PERIOD = 56  # .
        KB_ALT_LEFT = 57
        KB_ALT_RIGHT = 58
        KB_SHIFT_LEFT = 59
        KB_SHIFT_RIGHT = 60
        KB_TAB = 61
        KB_SPACE = 62  #
        KB_SYM = 63  # 修改输入法
        KB_EXPLORER = 64
        KB_ENVELOPE = 65  # mail?
        KB_ENTER = 66
        KB_DELETE = 67
        KB_DEKETE_FRONT = KB_DELETE  # 向左删除
        KB_GRAVE = 68  # `
        KB_MINUS = 69  # -
        KB_EQUALS = 70  # =
        KB_BRACKET_LEFT = 71  # ???
        KB_BRACKET_RIGHT = 72
        KB_BACKSLASH = 73  # \
        KB_SEMICOLON = 74  # ;
        KB_APOSTROPHE = 74  # '
        KB_SLASH = 75  # /
        KB_AT = 77  # @
        KB_NUM = 78
        KB_HEADSETHOOK = 79  # ???
        KB_PLUS = 81  # +
        KB_MIDIA_STOP = 86
        KB_MEDIA_NEXT = 87
        KB_MEDIA_PREVIOUS = 88
        KB_MEDIA_FAST_BACKWARD = 89
        KB_MEDIA_FAST_FORWARD = 90
        KB_PAGE_UP = 92
        KB_PAGE_DOWM = 93
        KB_PICTSYMBOLS = 94  # PICTURE SYMBOLS MODIFIER
        KB_SWITCH_CHARSET = 95  # SWITCH CHARSET MODIFIER
        KB_ESCAPE = 111
        KB_DELETE_BEHIND = 112  # 向右删除
        KB_CTRL_LEFT = 113
        KB_CTRL_RIGHT = 114
        KB_CAPS_LOCK = 115
        KB_SCROLL_LOCK = 116
        KB_META_LEFT = 117
        KB_META_RIGHT = 118
        KB_FUNCTION = 119  # Fn
        KB_PRINT_SCREEN = 120
        KB_SYSTEM_REQUEST = KB_PRINT_SCREEN
        KB_BREAK = 121
        KB_PAUSE = KB_BREAK
        KB_HOME = 122
        KB_END = 123
        KB_INSERT = 124
        KB_FORWARD = 125
        KB_MEDIA_PLAY = 126
        KB_MEDIA_PAUSE = 127
        KB_MEDIA_CLOSE = 128
        KB_MEDIA_EJECT = 129
        KB_MEDIA_RECORD = 130
        KB_F1 = 131
        KB_F2 = 132
        KB_F3 = 133
        KB_F4 = 134
        KB_F5 = 135
        KB_F6 = 136
        KB_F7 = 137
        KB_F8 = 138
        KB_F9 = 139
        KB_F10 = 140
        KB_F11 = 141
        KB_F12 = 142
        KB_NUM_LOCK = 143
        KB_NUM_0 = 144
        KB_NUM_1 = 145
        KB_NUM_2 = 146
        KB_NUM_3 = 147
        KB_NUM_4 = 148
        KB_NUM_5 = 149
        KB_NUM_6 = 150
        KB_NUM_7 = 151
        KB_NUM_8 = 152
        KB_NUM_9 = 153
        KB_NUM_DIVIDE = 154
        KB_NUM_MULTIPLY = 155
        KB_NUM_SUBTRACT = 156
        KB_NUM_ADD = 157
        KB_NUM_DOT = 158
        KB_NUM_COMMA = 159
        KB_NUM_ENTER = 160
        KB_NUM_EQUALS = 161
        KB_NUM_LEFT_PAREN = 162
        KB_NUM_RIGHT_PAREN = 163
        KB_INFO = 165
        KB_CHANNEL_UP = 166
        KB_CHANNEL_DOWN = 167
        KB_ZOOM_IN = 168
        KB_ZOOM_OUT = 169

        """ 游戏手柄按键 """
        GAME_A = 96
        GAME_B = 97
        GAME_C = 98
        GAME_X = 99
        GAME_Y = 100
        GAME_Z = 101
        GAME_L1 = 102
        GAME_R1 = 103
        GAME_L2 = 104
        GAME_R2 = 105
        GAME_THUMBL = 106  # left thumb button
        GAME_THUMBR = 107  # right thumb button
        GAME_START = 108
        GAME_SELECT = 109
        GAME_MODE = 110

        """ 其他设备 """
        DEV_TV = 170
        DEV_WINDOW = 171
        DEV_GUIDE = 172
        DEV_DVR = 173
        DEV_BOOKMARK = 174
        DEV_CAPTIONS = 175  # Toggle captions
        DEV_SETTINGS = 176
        DEV_TV_POWER = 177
        DEV_TV_INPUT = 178
        DEV_STB_POWER = 179  # set-top-box power
        DEV_STB_INPUT = 180
        DEV_AVR_POWER = 181  # A/V receiver power
        DEV_AVR_INPUT = 182
        DEV_KB_PROG_RED = 183  # Red "programmable"
        DEV_KB_PROG_GREEN = 184  # Green "programmable"

    def __init__(self):
        self.device_SN = None
        self.device_name = None
        self.device_transport = None

    @staticmethod
    def adb_command(cmd):
        """
        adb .....
        :param cmd: command
        :return: (successful:Boolean, output:bytes, error:bytes)
        """
        exe = "adb "
        p = subprocess.Popen(exe + cmd,
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        # p.wait()
        out, err = p.communicate()
        if p.returncode != 0:
            return False, out, err
        else:
            return True, out, None
        pass

    @staticmethod
    def start_server():
        p = subprocess.Popen("adb start-server",
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        return p.wait() is 0

    @staticmethod
    def kill_server():
        p = subprocess.Popen("adb kill-server",
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        return p.wait() is 0

    stop_server = kill_server

    @staticmethod
    def reset_server():
        return AdbInterface.kill_server() and AdbInterface.start_server()

    @staticmethod
    def get_adb_version():
        succ,out,err = AdbInterface.adb_command("version")
        adb_ver = None
        srv_ver = None
        adb_path = None
        if succ:
            out: str = out.decode()
            lines = out.splitlines()
            for l in lines:
                if "Installed" in l:
                    adb_path = l[13:]
                elif "Android Debug Bridge" in l:
                    adb_ver = l[29:]
                elif "Version" in l:
                    srv_ver = l[8:]
        else:
            print("Out:", out.decode())
            print("Error:", err.decode())
        return (adb_ver, srv_ver, adb_path)

    @staticmethod
    def list_devices():
        """
        List all devices
        :return: (Successful, [(SN, status)...])
        """
        succ,out,err = AdbInterface.adb_command("devices")
        if succ:
            out: str = out.decode()
            lines = out.splitlines()
            devs = []
            list_head = 0
            for l in lines:
                if "List of devices" not in l: list_head += 1
                else: break
            for i in range(list_head+1, len(lines)):
                line = lines[i]
                if len(line.strip()) is 0:
                    continue
                d = line.split(None, 2)
                if len(d) == 2:
                    devs.append((d[0].strip(), d[1].strip()))
            return True, devs
        else:
            print("Out:", out.decode())
            print("Error:", err.decode())
            return False, []

    @staticmethod
    def list_devices_detail():
        """
        List all devices
        :return: (Successful, [{"SN,"state","product","model","device","transport_id"}...])
        """
        succ, out, err = AdbInterface.adb_command("devices -l")
        if succ:
            out: str = out.decode()
            lines = out.splitlines()
            devs = []
            list_head = 0
            for l in lines:
                if "List of devices" not in l: list_head += 1
                else: break
            for i in range(list_head+1, len(lines)):
                line = lines[i]
                if len(line.strip()) is 0:
                    continue
                info = line.split()
                if len(info) > 2:  # 最少两个字段
                    dev = {"SN": info[0], "state": info[1]}
                    # 解析剩余kay-value
                    for i in range(2, len(info)):
                        kv = info[i].split(":")
                        if len(kv) is 2:
                            dev[kv[0]] = kv[1]
                        else:
                            dev["key_" + str(i)] = info[i]
                    devs.append(dev)
            return True, devs
        else:
            print("Out:", out.decode())
            print("Error:", err.decode())
            return False, []

    @staticmethod
    def wait_for_devices(sn=None, timeout=None):
        """
        wait for any/specific device online
        :param sn: specific device
        :param timeout:
        :return: online
        """
        dev = (" -s " + sn) if sn is not None else ""
        p = subprocess.Popen("adb" + dev + " wait-for-device",
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        try:
            return p.wait(timeout=timeout) is 0
        except subprocess.TimeoutExpired:
            return False

    @staticmethod
    def connect_tcpip(host :[str, int], port :int = None):
        if isinstance(host, int):
            ip_addr = str(host / 255 / 255 / 255 % 255) + "." + str(host / 255 / 255 % 255) + "." + str(host / 255 % 255) + "." + str(host % 255)
        else:
            ip_addr = str(host)
        if port:
            cmd = "connect {}:{}".format(ip_addr, port)
        else:
            cmd = "connect {}".format(ip_addr)
        succ, out, err = AdbInterface.adb_command(cmd)
        if succ:
            out = out.decode()
            return "connected" in out, out
        else:
            return False, err.decode()

    @staticmethod
    def disconnect_tcpip(host :[str, int], port :int = None):
        if isinstance(host, int):
            ip_addr = str(host / 255 / 255 / 255 % 255) + "." + str(host / 255 / 255 % 255) + "." + str(host / 255 % 255) + "." + str(host % 255)
        else:
            ip_addr = str(host)
        if port:
            cmd = "disconnect {}:{}".format(ip_addr, port)
        else:
            cmd = "disconnect {}".format(ip_addr)
        succ, out, err = AdbInterface.adb_command(cmd)
        if succ:
            out = out.decode()
            return "disconnected" in out, out
        else:
            return False, err.decode()

    def device_command(self, cmd):
        """
        adb .....
        :param cmd: command
        :return: (successful:Boolean, output:bytes, error:bytes)
        """
        if self.device_transport:
            dev = "-t {} ".format(self.device_transport)
        elif self.device_SN:
            dev = "-s {} ".format(self.device_SN)
        else:
            dev = ""
        return AdbInterface.adb_command(dev + cmd)

    def device_shell(self, args):
        """
        adb shell ......
        :param args: device command
        :return: (successful:Boolean, output:bytes, error:bytes)
        """
        return self.device_command("shell " + args)

    def set_device(self, sn: str = None, name: str = None, transport: [int,str] = None):
        """
        set current device
        :param sn: Device Serial Number
        :param name: Device Name
        :param transport: Transport ID
        :return successful: bool
        """
        cur_dev = None
        succ, devs = self.list_devices_detail()
        if not succ:
            return False
        if sn:
            for d in devs:
                if "SN" in d and d["SN"] == sn:
                    cur_dev = d
                    break
        elif transport:
            for d in devs:
                if "transport_id" in d and d["transport_id"] == str(transport):
                    cur_dev = d
                    break
        elif name:
            for d in devs:
                for k,v in d.items():
                    if v == name:
                        cur_dev = d
                        break
                if cur_dev:
                    break
        elif len(devs) > 0:
            cur_dev = devs[0]
        else:
            return False
        if cur_dev is None:
            return False
        self.device_SN = cur_dev["SN"] if "SN" in cur_dev else None
        self.device_name = name if name else cur_dev["model"] if "model" in cur_dev else None
        self.device_transport = cur_dev["transport_id"] if "transport_id" in cur_dev else None
        return True

    def get_device(self):
        """
        get current device
        :return: (Device Serial Number, others)
        """
        if self.device_SN is None:
            self.set_device()
        sn = ""
        cur_dev = {"name": self.device_name, "transport_id": self.device_transport}
        if self.device_SN is not None:
            sn = self.device_SN
            succ, devs = self.list_devices_detail()
            if succ:
                for d in devs:
                    if self.device_SN == d["SN"]:
                        cur_dev.update(d)
        return sn, cur_dev

    def check_device(self):
        """
        check device state
        :return: (is_online, state)
        """
        succ, out, err = self.device_command("get-state")
        if succ:
            state = out.decode().strip()
            succ = not (state == "unknown")
            return succ, state
        else:
            return succ, err.decode().strip()

    def wait_for_device(self, timeout=None):
        """
        Wait for current device online again
        :param timeout:
        :return: online
        """
        if self.device_SN is None:
            return False
        else:
            return AdbInterface.wait_for_devices(sn=self.device_SN, timeout=timeout)

    def listen_tcpip(self, port:int=5555):
        """
        Restart adbd listening on TCP on PORT
        :param port: tcp port
        :return: successful, message
        """
        succ,out,err = self.device_command("tcpip {}".format(port))
        if succ:
            out = out.decode()
            return "restarting" in out, out
        else:
            return False, err.decode()

    def listen_usb(self):
        """
        Restart adbd listening on USB. This will disconnect TCP mode.
        :return: successful, message
        """
        succ,out,err = self.device_command("usb")
        if succ:
            out = out.decode()
            return "restarting" in out, out
        else:
            return False, err.decode()

    def press_key(self, key: KeyCode):
        """
        Press a key on device
        :param key: KeyCode
        :return: successful, message
        """
        arg = "input keyevent " + str(key.value)
        succ, out, err = self.device_shell(arg)
        if succ:
            return True, "OK"
        else:
            return False, err.decode()

    def press_power(self):
        """
        Press `power` key on device
        :return: successful, message
        """
        return self.press_key(self.KeyCode.POWER)

    def press_vol_up(self):
        """
        Press `vol+` key on device
        :return: successful, message
        """
        return self.press_key(self.KeyCode.VOL_UP)

    def press_vol_down(self):
        """
        Press `vol-` key on device
        :return: successful, message
        """
        return self.press_key(self.KeyCode.VOL_DOWN)

    def press_vol_mute(self):
        """
        Press `mute` key on device
        :return: successful, message
        """
        return self.press_key(self.KeyCode.VOL_MUTE)

    def press_back(self):
        """
        Press `back` key on device
        :return: successful, message
        """
        return self.press_key(self.KeyCode.BACK)

    def press_home(self):
        """
        Press `home` key on device
        :return: successful, message
        """
        return self.press_key(self.KeyCode.HOME)

    def press_menu(self):
        """
        Press `menu` key on device
        :return: successful, message
        """
        return self.press_key(self.KeyCode.MENU)

    def input_text(self, s: str):
        """
        Input a string to device
        :return: successful, message
        """
        arg = "input text " + str(s)
        succ, out, err = self.device_shell(arg)
        if succ:
            return True, "OK"
        else:
            return False, err.decode()

    def click(self, x, y):
        """
        Click screen
        :param x: position X
        :param y: position Y
        :return: (successful, message)
        """
        arg = "input tap " + str(x) + " " + str(y)
        succ, out, err = self.device_shell(arg)
        if succ:
            return True, "OK"
        else:
            return False, err.decode()

    def slide(self, x1, y1, x2, y2, time=100):
        """
        Drag from (x1,y1) to (x2,y2) in (time) ms.
        :param x1: position1 X
        :param y1: position1 Y
        :param x2: position2 X
        :param y2: position2 Y
        :param time: action time (ms)
        :return: (successful, message)
        """
        arg = "input swipe " \
              + str(x1) + " " + str(y1) + " " \
              + str(x2) + " " + str(y2) + " " + str(time)
        succ, out, err = self.device_shell(arg)
        if succ:
            return True, "OK"
        else:
            return False, err.decode()

    def press(self, x, y, time):
        """
        Press (x,y) in (time) ms
        :param x: position X
        :param y: position Y
        :param time: ms
        :return: (successful, message)
        """
        return self.slide(x, y, x, y, time)

    def get_screen_size(self):
        """
        Get screen size
        :return: (successful, (width, height))
        """
        arg = "dumpsys window displays"
        succ, out, err = self.device_shell(arg)
        x = -1
        y = -1
        if succ:
            out: str = out.decode()
            # e.g. init=1080x2340 480dpi cur=2340x1080 app=2255x1080
            idx = out.find("cur=")
            if idx >= 0:
                idx0 = idx + 4
                idx1 = out.find("x", idx0)
                idx2 = out.find(" ", idx0)
                if idx1 > 0 and idx2 > 0:
                    x = int(out[idx0:idx1], 10)
                    y = int(out[idx1 + 1:idx2], 10)
                    succ = True
                else:
                    succ = False
            else:
                succ = False
        return succ, (x, y)

    def capture_screen(self, path: str = None, img_lib: str = "PIL"):
        """
        Capture screen and save
        :param path:
                    If path is not None, save image to path, else return data.
        :param img_lib:
                    The type of image object. such as: PIL, cv2, imageio, matplotlib.
        :return (successful, image)
                    image: {PIL         => PIL.Image,
                            cv2         => numpy.ndarray,
                            imageio     => imageio.core.util.Array,
                            matplotlib  => numpy.ndarray,
                            wx          => wx.Image
                           }
                    The type of image is depend on param img_lib.
        """
        arg = "screencap -p"
        succ, out, err = self.device_shell(arg)
        msg = None
        if succ:
            # Verify data, first 8bytes, up to 16.
            # PNG, 4bytes
            if not out.startswith(b"\x89\x50\x4e\x47"):
                return False, "Bad data format(not PNG stream)."
            # 0x1A, 7th byte, index is 6
            idx_1A = out.find(b'\x1a', 0, 16)
            if idx_1A < 0:
                return False, "Bad data format(no 0x1A flag)."
            # 0x0D...0x0A, maybe insert 0x0D by windows system
            for b in out[4:idx_1A - 1]:
                if b is not 0x0D:
                    return False, "Bad data format(not only 0x0D)."
            if out[idx_1A - 1] is not 0x0A:
                return False, "Bad data format(no 0x0A)."
            # remove redundant 0x0D
            if idx_1A > 6:
                arr = bytearray(out)
                arr = arr.replace(b'\r' * (idx_1A - 6) + b'\n', b'\n')
                out = bytes(arr)
            # Verify again
            if not out.startswith(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"):
                return False, "Bad data format(bad PNG head)."
            # process image
            if path is not None:
                if not isinstance(path, str):
                    return False, "Bad type of 'path'"
                if path.endswith(".png"):
                    # save file directly
                    with open(path, "wb+") as f:
                        f.write(out)
                    return True, "OK"
            # set image lib
            if isinstance(img_lib, str):
                img_lib = img_lib.lower()
            else:
                img_lib = "pil"
            if img_lib == "pil" or img_lib == "pillow":
                import PIL.Image
                image = PIL.Image.open(io.BytesIO(out))
                if image is None:
                    msg = "Fail to convert binary data to PIL.Image."
                    succ = False
                elif path is None:
                    msg = image
                else:
                    image.save(path)
                    msg = "OK"
            elif img_lib == "cv2" or img_lib == "cv":
                import cv2
                import numpy
                imagedata = numpy.frombuffer(out, numpy.uint8)
                image = cv2.imdecode(imagedata, cv2.IMREAD_COLOR)
                if image is None:
                    msg = "Fail to convert binary data to cv2:numpy.ndarray."
                    succ = False
                elif path is None:
                    msg = image
                else:
                    cv2.imwrite(path, image)
                    msg = "OK"
            elif img_lib == "imageio":
                import imageio
                image = imageio.imread(out)
                if image is None:
                    msg = "Fail to convert binary data to imageio.Array."
                    succ = False
                elif path is None:
                    msg = image
                else:
                    imageio.imwrite(path, image)
                    msg = "OK"
            elif img_lib == "matplotlib" or img_lib == "matplot" or img_lib == "pyplot":
                import matplotlib.pyplot as plt
                image = plt.imread(io.BytesIO(out))
                if image is None:
                    msg = "Fail to convert binary data to matplotlib:numpy.ndarray."
                    succ = False
                elif path is None:
                    msg = image
                else:
                    plt.imsave(path, image)
                    msg = "OK"
            elif img_lib == "wx" or img_lib == "wxpython":
                import wx
                from wx import Image as wxImage
                io_out = io.BytesIO(out)
                image = wxImage(io_out, type=wx.BITMAP_TYPE_PNG)
                if image is None:
                    msg = "Fail to convert binary data to wx.Image."
                    succ = False
                elif path is None:
                    msg = image
                else:
                    succ = image.SaveFile(path)
                    msg = "OK" if succ else "Fail to save file."
            else:
                msg = "Unrecognized image library."
                succ = False
        else:
            msg = err.decode()
        return succ, msg

    def get_screen_locked(self):
        """
        Whether the screen is locked
        :return: successful, locked or message
        """
        arg = "dumpsys window policy"
        succ, out, err = self.device_shell(arg)
        if succ:
            out: str = out.decode()
            # e.g. mShowingLockscreen=true
            idx = out.find("mShowingLockscreen=")
            if idx > 0:
                s = out[idx + 19:idx + 23]
                if s in "true":
                    return True, True
                elif s in "false":
                    return True, False
                else:
                    return False, "(ShowLock)Unexpected:" + s
            # Not found(mShowingLockscreen).
            # e.g. isStatusBarKeyguard=true
            idx = out.find("isStatusBarKeyguard=")
            if idx > 0:
                s = out[idx + 20:idx + 24]
                if s in "true":
                    return True, True
                elif s in "false":
                    return True, False
                else:
                    return False, "(Keyguard)Unexpected:" + s
            # android 10
            # KeyguardStateMonitor\r\n        mIsShowing=false
            idx = out.find("KeyguardStateMonitor")
            if idx > 0:
                idx = out.find("mIsShowing=", idx)
                if idx > 0:
                    s = out[idx + 11:idx + 16].strip()
                    if s in "true":
                        return True, True
                    elif s in "false":
                        return True, False
                    else:
                        return False, "(KeyguardStateMonitor.IsShowing)Unexpected:" + s
                else:
                    return False, "Not Found(KeyguardStateMonitor.IsShowing)."
            return False, "Not found."
        else:
            return False, err.decode()
        pass

    def get_screen_on(self):
        """
        Whether the screen is activated
        :return: successful, activated or message
        """
        arg = "dumpsys window policy"
        succ, out, err = self.device_shell(arg)
        if succ:
            out: str = out.decode()
            # e.g. mAwake=true
            idx = out.find("mAwake=")
            if idx > 0:
                s = out[idx + 7:idx + 12].strip()
                if s in "true":
                    return True, True
                elif s in "false":
                    return True, False
                else:
                    return False, "Unexpected:" + s
            # for android 10, screenState=SCREEN_STATE_OFF/SCREEN_STATE_ON
            idx = out.find("screenState=")
            if idx > 0:
                s = out[idx + 12:idx + 28].strip()
                if s in "SCREEN_STATE_ON":
                    return True, True
                elif s in "SCREEN_STATE_OFF":
                    return True, False
                else:
                    return False, "Unexpected:" + s
            return False, "Not found."
        else:
            return False, err.decode()
        pass

    def get_screen_off(self):
        """
        Whether the screen is closed
        :return: successful, closed or message
        """
        succ, state = self.get_screen_on()
        if succ:
            state = bool(not state)
        return succ, state

    def get_screen_rotation(self):
        """
        Get the rotation state of screen
        :return: successful, angle or message
        """
        def find_mCurrentRotation(param):
            """:return (succ, message)"""
            arg = "dumpsys window " + param
            succ, out, err = self.device_shell(arg)
            if succ:
                out: str = out.decode()
                # e.g. mAwake=true
                idx = out.find("mCurrentRotation=ROTATION_")
                if idx > 0:
                    idx2 = out.find('\n', idx + 26, idx + 32)
                    if idx2 > 0:
                        s = out[idx + 26:idx2]
                        s = s.strip()
                        try:
                            return True, int(s)
                        except:
                            return False, "Not number."
                    return False, "Too long value."
                return False, "Not found."
            else:
                return False, err.decode()
        succ, msg = find_mCurrentRotation("policy")
        if not succ:
            succ, msg = find_mCurrentRotation("displays")
        return succ, msg

    def get_wlan_ip(self):
        """
        Get ip address of WLAN
        :return successful, address/mask or message
        """
        succ, out, err = self.device_shell("netcfg")
        if succ:
            out: str = out.decode()
            lines = out.splitlines()
            for interface in lines:
                if interface.startswith("wlan"):
                    items = interface.split()
                    if len(items) == 4:
                        return True, items[2]
                    else:
                        for it in items:
                            if "." in it and '/' in it:
                                return True, it
                    return False, "Not found IP in: `" + out + "`"
        else:
            return False, err.decode()

    def push_file(self, local_path, remote_path):
        """
        Push file from local to device
        :param local_path:
        :param remote_path:
        :return: successful, message
        """
        arg = "push " + str(local_path) + " " + str(remote_path)
        succ, out, err = self.device_command(arg)
        if succ:
            return True, "OK"
        else:
            return False, err.decode()

    def pull_file(self, remote_path, local_path):
        """
        Pull file from device to local
        :param remote_path:
        :param local_path:
        :return: successful, message
        """
        arg = "pull " + str(remote_path) + " " + str(local_path)
        succ, out, err = self.device_command(arg)
        if succ:
            return True, "OK"
        else:
            return False, err.decode()

    def delete_file(self, remote_path):
        """
        Delete file in device
        :param remote_path:
        :return: successful, message
        """
        arg = "rm -rf " + str(remote_path)
        succ, out, err = self.device_shell(arg)
        if succ:
            return True, "OK"
        else:
            return False, err.decode()


if __name__ == "__main__":
    AdbInterface.get_adb_version()
    # print("Server:", AdbInterface.reset_server())
    succ, devs = AdbInterface.list_devices_detail()
    print("Devices list:")
    for d in devs:
        print(d)
    adb = AdbInterface()
    sn, name = adb.get_device()
    print("Current device:")
    print("SN:", sn, " \tName:", name)
    succ, state = adb.check_device()
    print("Check device:", succ, "online" if state == "device" else "offline")
    # succ, pos = adb.get_screen_size()
    # print("Screen size:", succ, pos)
    # succ, msg = adb.click(655, 319)
    # succ, image = adb.capture_screen()
    # print("Capture size:", succ, image.size)
    # succ, status = adb.get_screen_on()
    # print("Screen ON:", succ, status)
    # succ, status = adb.get_screen_locked()
    # print("Screen locked:", succ, status)
    # succ, status = adb.get_screen_rotation()
    # print("Screen rotation:", succ, status)
    # succ, msg = adb.push_file("adb_interface.py", '/data/local/tmp/test.txt')
    # print("Push file:", succ, msg)
    # succ, msg = adb.pull_file('/data/local/tmp/test.txt', "test.txt")
    # print("Pull file:", succ, msg)
    # import os
    # if os.path.exists("test.txt"):
    #     os.remove("test.txt")
    # else:
    #     print("'test.txt' don't exist.")
    # succ, msg = adb.delete_file('/data/local/tmp/test.txt')
    # print("Delete file:", succ, msg)
    print("WLAN IP:", adb.get_wlan_ip())
